#pragma once

#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <typeinfo>
#include <utility>
#include <variant>
#include <vector>

#include "drake/common/drake_assert.h"
#include "drake/common/ssize.h"
#include "drake/common/unused.h"
#include "drake/systems/framework/abstract_value_cloner.h"
#include "drake/systems/framework/cache_entry.h"
#include "drake/systems/framework/framework_common.h"
#include "drake/systems/framework/input_port_base.h"
#include "drake/systems/framework/output_port_base.h"
#include "drake/systems/framework/value_producer.h"

namespace drake {
namespace systems {

#ifndef DRAKE_DOXYGEN_CXX
namespace internal {
// This class is defined later in this header file, below.
class DiagramSystemBaseAttorney;
}  // namespace internal
#endif

/** Provides non-templatized functionality shared by the templatized System
classes.

Terminology: in general a Drake System is a tree structure composed of
"subsystems", which are themselves System objects. The corresponding Context is
a parallel tree structure composed of "subcontexts", which are themselves
Context objects. There is a one-to-one correspondence between subsystems and
subcontexts. Within a given System (Context), its child subsystems (subcontexts)
are indexed using a SubsystemIndex; there is no separate SubcontextIndex since
the numbering must be identical. */
class SystemBase : public internal::SystemMessageInterface {
 public:
  DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(SystemBase);

  ~SystemBase() override;

  /** Sets the name of the system. Do not use the path delimiter character ':'
  in the name. When creating a Diagram, names of sibling subsystems should be
  unique. DiagramBuilder uses this method to assign a unique default name if
  none is provided. */
  // TODO(sherm1) Enforce reasonable naming policies.
  void set_name(const std::string& name) { name_ = name; }

  /** Returns the name last supplied to set_name(), if any. Diagrams built with
  DiagramBuilder will always have a default name for every contained subsystem
  for which no user-provided name is available. Systems created by copying with
  a scalar type change have the same name as the source system. An empty string
  is returned if no name has been set. */
  // TODO(sherm1) This needs to be better distinguished from the human-readable
  // name. Consider an api change like get_label() for this one, with the
  // intent that the label could be used programmatically.
  const std::string& get_name() const { return name_; }

  /** Returns a name for this %System based on a stringification of its type
  name and memory address.  This is intended for use in diagnostic output
  and should not be used for behavioral logic, because the stringification
  of the type name may produce differing results across platforms and
  because the address can vary from run to run. */
  std::string GetMemoryObjectName() const;

  /** Returns a human-readable name for this system, for use in messages and
  logging. This will be the same as returned by get_name(), unless that would
  be an empty string. In that case we return a non-unique placeholder name,
  currently just "_" (a lone underscore). */
  const std::string& GetSystemName() const final {
    return name_.empty() ? internal::SystemMessageInterface::no_name() : name_;
  }

  /** Generates and returns a human-readable full path name of this subsystem,
  for use in messages and logging. The name starts from the root System, with
  "::" delimiters between parent and child subsystems, with the individual
  subsystems represented by their names as returned by GetSystemName(). */
  std::string GetSystemPathname() const final;

  /** Returns the most-derived type of this concrete System object as a
  human-readable string suitable for use in error messages. The format is as
  generated by NiceTypeName and will include namespace qualification if
  present.
  @see NiceTypeName for more specifics. */
  std::string GetSystemType() const final { return NiceTypeName::Get(*this); }

  /** Returns a Context suitable for use with this System. Context resources
  are allocated based on resource requests that were made during System
  construction. */
  std::unique_ptr<ContextBase> AllocateContext() const {
    // Get a concrete Context of the right type, allocate internal resources
    // like parameters, state, and cache entries, and set up intra- and
    // inter-subcontext dependencies.
    std::unique_ptr<ContextBase> context = DoAllocateContext();

    // We depend on derived classes to call our InitializeContextBase() method
    // after allocating the appropriate concrete Context.
    DRAKE_DEMAND(
        internal::SystemBaseContextBaseAttorney::is_context_base_initialized(
            *context));

    return context;
  }

  //----------------------------------------------------------------------------
  /** @name                      Graphviz methods */
  //@{

  /** Returns a Graphviz string describing this %System. To render the string,
  use the <a href="http://www.graphviz.org/">Graphviz</a> tool, ``dot``.

  Notes about the display conventions:

  - The nodes of the graph are systems, and the solid edges are connections
    between system input and output ports.

  - The class name of a %System is shown in <b>Bold</b> atop the node.

  - Under the class name, if a %System has been given a name via set_name(),
    it will be displayed as `name=...`.

  - Systems can elect to display additional properties besides their name;
    see GraphvizFragmentParams::header_lines for implementation details.

  - A Diagram's input ports are shown with a
    <span style="border:2px solid blue;border-radius:4px">blue border</span>
    and output ports are shown with a
    <span style="border:2px solid green;border-radius:4px">green border</span>.

  - Zero-sized ports are <span style="color:grey">greyed out</span>.

  - Deprecated ports are <strike>struck through</strike> and flagged with a
    headstone emoji (🪦) after their name.

  <!-- TODO(jwnimmer-tri) Add rendering of network connections.
  - Connections other than input-output ports (e.g., network message passing)
    are shown with dashed lines.
  -->

  @param max_depth Sets a limit to the depth of nested diagrams to visualize.
  Use zero to render a diagram as a single system block.

  @param options Arbitrary strings to request alterations to the output. Options
  that are unknown will be silently skipped. These options are often bespoke
  flags that are only understood by particular systems, but Drake has one
  built-in flag that is generally applicable: `"split"`. When set to `"I/O"`,
  the system will be added as two nodes with all inputs on one node and all
  outputs on the other; this is useful for systems that might otherwise cause
  problematic visual cycles.

  Options are applied only to this immediate system; they are not inherited by
  the subsystems of a %Diagram. To specify an option for a %Diagram's subsystem,
  prefix the option name with the subsystem's path, e.g., use
  `"plant/split"="I/O"` to set the `"split"` option on the subsystem named
  `"plant"`. */
  std::string GetGraphvizString(
      std::optional<int> max_depth = {},
      const std::map<std::string, std::string>& options = {}) const;

  /** (Advanced) The return type of GetGraphvizFragment(). */
  struct GraphvizFragment {
    /** The Graphviz IDs for this %System's input ports, to be used for adding
    Graphviz edges. The i'th element is the ID for `get_input_port(i)`. For a
    typical LeafSystem these will look like "s123:u0", "s123:u1", … but for
    diagrams and other special cases they might vary. */
    std::vector<std::string> input_ports;

    /** The Graphviz IDs for this %System's output ports, to be used for adding
    Graphviz edges. The i'th element is the ID for `get_output_port(i)`. For a
    typical LeafSystem these will look like "s123:y0", "s123:y1", … but for
    diagrams and other special cases they might vary. */
    std::vector<std::string> output_ports;

    /** The Graphviz content for this %System. The fragments must be a valid
    Graphviz figure when concatenated and then wrapped in a `digraph { … }` or
    `subgraph { … }` stanza. During concatenation, no extra newlines or any
    other kind of whitespace should be inserted. (This is a list of strings,
    rather than a single string, to avoid redundant string concatenation until
    the last moment when we return the final GetGraphvizString().) */
    std::vector<std::string> fragments;
  };

  /** (Advanced) Like GetGraphvizString() but does not wrap the string in a
  `digraph { … }`. This is useful when merging the fragment into another
  graph, and is how %Diagram obtains the Graphviz content for its subsystems.
  The parameters are identical to GetGraphvizString(). The return value contains
  additional metadata beyond the Graphviz content, to better support merging. */
  GraphvizFragment GetGraphvizFragment(
      std::optional<int> max_depth = {},
      const std::map<std::string, std::string>& options = {}) const;
  //@}

  /** (Advanced) The arguments to the protected method DoGetGraphvizFragment().
  This struct typically is only used by subclasses of LeafSystem that need to
  customize their Graphviz representation. These parameters constitute a polite
  request; a user's %System's implementation of DoGetGraphvizFragment() is not
  strictly required to honor any of these parameters, but generally should
  attempt to honor as many as possible. */
  struct GraphvizFragmentParams {
    /** As per GetGraphvizString(). */
    int max_depth{};

    /** As per GetGraphvizString(). */
    std::map<std::string, std::string> options;

    /** The Graphviz ID to use for this node. */
    std::string node_id;

    /** The header line(s) to use for this Graphviz node's table. The strings in
    `header_lines` should not contain newlines; those are added automatically,
    along with `<BR/>` breaks between lines. */
    std::vector<std::string> header_lines;
  };

  //----------------------------------------------------------------------------
  /** @name            Input port evaluation (deprecated)
  These _deprecated_ methods provide scalar type-independent evaluation of a
  System input port in a particular Context. Instead of these, prefer to use
  InputPort::Eval(), acquiring the port with `get_input_port(port_index)`.

  If necessary, the methods here first cause the port's value
  to become up to date, then they return a reference to the now-up-to-date value
  in the given Context.

  Specified preconditions for these methods operate as follows: The
  preconditions will be checked in Debug builds but some or all might not be
  checked in Release builds for performance reasons. If we do check, and a
  precondition is violated, an std::exception will be thrown with a helpful
  message.

  @see System::get_input_port(), InputPort::Eval() for scalar type-specific
  input port access. */
  //@{

  // TODO(jwnimmer-tri) Deprecate me.
  /** Returns the value of the input port with the given `port_index` as an
  AbstractValue, which is permitted for ports of any type. Causes the value to
  become up to date first if necessary, delegating to our parent Diagram.
  Returns a pointer to the port's value, or nullptr if the port is not
  connected. If you know the actual type, use one of the more-specific
  signatures.

  @pre `port_index` selects an existing input port of this System.

  @see InputPort::Eval() (preferred) */
  const AbstractValue* EvalAbstractInput(const ContextBase& context,
                                         int port_index) const {
    ValidateContext(context);
    if (port_index < 0) {
      ThrowNegativePortIndex(__func__, port_index);
    }
    const InputPortIndex port(port_index);
    return EvalAbstractInputImpl(__func__, context, port);
  }

  // TODO(jwnimmer-tri) Deprecate me.
  /** Returns the value of an abstract-valued input port with the given
  `port_index` as a value of known type `V`. Causes the value to become
  up to date first if necessary. See EvalAbstractInput() for
  more information.

  The result is returned as a pointer to the input port's value of type `V`,
  or nullptr if the port is not connected.

  @pre `port_index` selects an existing input port of this System.
  @pre the port's value must be retrievable from the stored abstract value
       using `AbstractValue::get_value<V>`.

  @see InputPort::Eval() (preferred)
  @tparam V The type of data expected. */
  template <typename V>
  const V* EvalInputValue(const ContextBase& context, int port_index) const {
    ValidateContext(context);
    if (port_index < 0) {
      ThrowNegativePortIndex(__func__, port_index);
    }
    const InputPortIndex port(port_index);

    const AbstractValue* const abstract_value =
        EvalAbstractInputImpl(__func__, context, port);
    if (abstract_value == nullptr) {
      return nullptr;  // An unconnected port.
    }

    // We have a value, is it a V?
    const V* const value = abstract_value->maybe_get_value<V>();
    if (value == nullptr) {
      ThrowInputPortHasWrongType(__func__, port, NiceTypeName::Get<V>(),
                                 abstract_value->GetNiceTypeName());
    }

    return value;
  }
  //@}

  /** Returns the number of input ports currently allocated in this System.
  These are indexed from 0 to %num_input_ports()-1. */
  int num_input_ports() const { return ssize(input_ports_); }

  /** Returns the number of output ports currently allocated in this System.
  These are indexed from 0 to %num_output_ports()-1. */
  int num_output_ports() const { return ssize(output_ports_); }

  /** Returns a reference to an InputPort given its `port_index`.
  @pre `port_index` selects an existing input port of this System. */
  const InputPortBase& get_input_port_base(InputPortIndex port_index) const {
    return GetInputPortBaseOrThrow(__func__, port_index,
                                   /* warn_deprecated = */ true);
  }

  /** Returns a reference to an OutputPort given its `port_index`.
  @pre `port_index` selects an existing output port of this System. */
  const OutputPortBase& get_output_port_base(OutputPortIndex port_index) const {
    return GetOutputPortBaseOrThrow(__func__, port_index,
                                    /* warn_deprecated = */ true);
  }

  /** Returns the total dimension of all of the vector-valued input ports (as if
  they were muxed). */
  int num_total_inputs() const {
    int count = 0;
    for (const auto& in : input_ports_) count += in->size();
    return count;
  }

  /** Returns the total dimension of all of the vector-valued output ports (as
  if they were muxed). */
  int num_total_outputs() const {
    int count = 0;
    for (const auto& out : output_ports_) count += out->size();
    return count;
  }

  /** Reports all direct feedthroughs from input ports to output ports. For
  a system with m input ports: `I = i₀, i₁, ..., iₘ₋₁`, and n output ports,
  `O = o₀, o₁, ..., oₙ₋₁`, the return map will contain pairs (u, v) such that

  - 0 ≤ u < m,
  - 0 ≤ v < n,
  - and there _might_ be a direct feedthrough from input iᵤ to each output oᵥ.

  See @ref DeclareLeafOutputPort_feedthrough "DeclareLeafOutputPort"
  documentation for how leaf systems can report their feedthrough.
  */
  virtual std::multimap<int, int> GetDirectFeedthroughs() const = 0;

  /** Returns the number nc of cache entries currently allocated in this System.
  These are indexed from 0 to nc-1. */
  int num_cache_entries() const { return ssize(cache_entries_); }

  /** Returns a reference to a CacheEntry given its `index`. */
  const CacheEntry& get_cache_entry(CacheIndex index) const {
    DRAKE_ASSERT(0 <= index && index < num_cache_entries());
    return *cache_entries_[index];
  }

  /** (Advanced) Returns a mutable reference to a CacheEntry given its `index`.
  Note that you do not need mutable access to a CacheEntry to modify its value
  in a Context, so most users should not use this method. */
  CacheEntry& get_mutable_cache_entry(CacheIndex index) {
    DRAKE_ASSERT(0 <= index && index < num_cache_entries());
    return *cache_entries_[index];
  }

  //============================================================================
  /** @name                     Dependency tickets
  @anchor DependencyTicket_documentation

  Use these tickets to declare well-known sources as prerequisites of a
  downstream computation such as an output port, derivative, update, or cache
  entry. The ticket numbers for the built-in sources are the same for all
  systems. For time and accuracy they refer to the same global resource;
  otherwise they refer to the specified sources within the referencing system.

  A dependency ticket for a more specific resource (a particular input or
  output port, a discrete variable group, abstract state variable, a parameter,
  or a cache entry) is allocated and stored with the resource when it is
  declared. Usually the tickets are obtained directly from the resource but
  you can recover them with methods here knowing only the resource index. */
  //@{

  // The DependencyTrackers associated with these tickets are allocated
  // in ContextBase::CreateBuiltInTrackers() and the implementation there must
  // be kept up to date with the API contracts here.

  // The ticket methods are promoted in the System<T> class so that users can
  // invoke them in their constructors without prefixing with this->. If you
  // add, remove, rename, or reorder any of these be sure to update the
  // promotions in system.h.

  // Keep the order here the same as they are defined in the internal enum
  // BuiltInTicketNumbers, with the "particular resource" indexed methods
  // inserted prior to the corresponding built-in "all such resources" ticket.

  /** Returns a ticket indicating that a computation does not depend on *any*
  source value; that is, it is a constant. If this appears in a prerequisite
  list, it must be the only entry. */
  static DependencyTicket nothing_ticket() {
    return DependencyTicket(internal::kNothingTicket);
  }

  /** Returns a ticket indicating dependence on time. This is the same ticket
  for all systems and refers to the same time value. */
  static DependencyTicket time_ticket() {
    return DependencyTicket(internal::kTimeTicket);
  }

  /** Returns a ticket indicating dependence on the accuracy setting in the
  Context. This is the same ticket for all systems and refers to the same
  accuracy value. */
  static DependencyTicket accuracy_ticket() {
    return DependencyTicket(internal::kAccuracyTicket);
  }

  /** Returns a ticket indicating that a computation depends on configuration
  state variables q. There is no ticket representing just one of the state
  variables qᵢ. */
  static DependencyTicket q_ticket() {
    return DependencyTicket(internal::kQTicket);
  }

  /** Returns a ticket indicating dependence on velocity state variables v. This
  does _not_ also indicate a dependence on configuration variables q -- you must
  list that explicitly or use kinematics_ticket() instead. There is no ticket
  representing just one of the state variables vᵢ. */
  static DependencyTicket v_ticket() {
    return DependencyTicket(internal::kVTicket);
  }

  /** Returns a ticket indicating dependence on any or all of the miscellaneous
  continuous state variables z. There is no ticket representing just one of
  the state variables zᵢ. */
  static DependencyTicket z_ticket() {
    return DependencyTicket(internal::kZTicket);
  }

  /** Returns a ticket indicating dependence on _all_ of the continuous
  state variables q, v, or z. */
  static DependencyTicket xc_ticket() {
    return DependencyTicket(internal::kXcTicket);
  }

  /** Returns a ticket indicating dependence on a particular discrete state
  variable xdᵢ (may be a vector). (We sometimes refer to this as a "discrete
  variable group".)
  @see xd_ticket() to obtain a ticket for _all_ discrete variables. */
  DependencyTicket discrete_state_ticket(DiscreteStateIndex index) const {
    return discrete_state_tracker_info(index).ticket;
  }

  /** Returns a ticket indicating dependence on all of the numerical
  discrete state variables, in any discrete variable group.
  @see discrete_state_ticket() to obtain a ticket for just one discrete
       state variable. */
  static DependencyTicket xd_ticket() {
    return DependencyTicket(internal::kXdTicket);
  }

  /** Returns a ticket indicating dependence on a particular abstract state
  variable xaᵢ.
  @see xa_ticket() to obtain a ticket for _all_ abstract variables. */
  DependencyTicket abstract_state_ticket(AbstractStateIndex index) const {
    return abstract_state_tracker_info(index).ticket;
  }

  /** Returns a ticket indicating dependence on all of the abstract
  state variables in the current Context.
  @see abstract_state_ticket() to obtain a ticket for just one abstract
       state variable. */
  static DependencyTicket xa_ticket() {
    return DependencyTicket(internal::kXaTicket);
  }

  /** Returns a ticket indicating dependence on _all_ state variables x in this
  system, including continuous variables xc, discrete (numeric) variables xd,
  and abstract state variables xa. This does not imply dependence on time,
  accuracy, parameters, or inputs; those must be specified separately. If you
  mean to express dependence on all possible value sources, use
  all_sources_ticket() instead. */
  static DependencyTicket all_state_ticket() {
    return DependencyTicket(internal::kXTicket);
  }

  /** Returns a ticket indicating dependence on a particular numeric parameter
  pnᵢ (may be a vector).
  @see pn_ticket() to obtain a ticket for _all_ numeric parameters. */
  DependencyTicket numeric_parameter_ticket(NumericParameterIndex index) const {
    return numeric_parameter_tracker_info(index).ticket;
  }

  /** Returns a ticket indicating dependence on all of the numerical
  parameters in the current Context.
  @see numeric_parameter_ticket() to obtain a ticket for just one numeric
       parameter. */
  static DependencyTicket pn_ticket() {
    return DependencyTicket(internal::kPnTicket);
  }

  /** Returns a ticket indicating dependence on a particular abstract
  parameter paᵢ.
  @see pa_ticket() to obtain a ticket for _all_ abstract parameters. */
  DependencyTicket abstract_parameter_ticket(
      AbstractParameterIndex index) const {
    return abstract_parameter_tracker_info(index).ticket;
  }

  /** Returns a ticket indicating dependence on all of the abstract
  parameters pa in the current Context.
  @see abstract_parameter_ticket() to obtain a ticket for just one abstract
       parameter. */
  static DependencyTicket pa_ticket() {
    return DependencyTicket(internal::kPaTicket);
  }

  /** Returns a ticket indicating dependence on _all_ parameters p in this
  system, including numeric parameters pn, and abstract parameters pa. */
  static DependencyTicket all_parameters_ticket() {
    return DependencyTicket(internal::kAllParametersTicket);
  }

  /** Returns a ticket indicating dependence on input port uᵢ indicated
  by `index`.
  @pre `index` selects an existing input port of this System. */
  DependencyTicket input_port_ticket(InputPortIndex index) const {
    DRAKE_DEMAND(0 <= index && index < num_input_ports());
    if (input_ports_[index]->get_deprecation().has_value())
      WarnPortDeprecation(/* is_input = */ true, index);
    return input_ports_[index]->ticket();
  }

  /** Returns a ticket indicating dependence on _all_ input ports u of this
  system.
  @see input_port_ticket() to obtain a ticket for just one input port. */
  static DependencyTicket all_input_ports_ticket() {
    return DependencyTicket(internal::kAllInputPortsTicket);
  }

  /** Returns a ticket indicating dependence on every possible independent
  source value _except_ input ports. This can be helpful in avoiding the
  incorrect appearance of algebraic loops in a Diagram (those always involve
  apparent input port dependencies). For an output port, use this ticket plus
  tickets for just the input ports on which the output computation _actually_
  depends. The sources included in this ticket are: time, accuracy, state,
  and parameters. Note that dependencies on cache entries are _not_ included
  here. Usually that won't matter since cache entries typically depend on at
  least one of time, accuracy, state, or parameters so will be invalidated for
  the same reason the current computation is. However, for a computation that
  depends on a cache entry that depends only on input ports, be sure that
  you have included those input ports in the dependency list, or include a
  direct dependency on the cache entry.

  @see input_port_ticket() to obtain a ticket for an input port.
  @see cache_entry_ticket() to obtain a ticket for a cache entry.
  @see all_sources_ticket() to also include all input ports as dependencies. */
  static DependencyTicket all_sources_except_input_ports_ticket() {
    return DependencyTicket(internal::kAllSourcesExceptInputPortsTicket);
  }

  /** Returns a ticket indicating dependence on every possible independent
  source value, including time, accuracy, state, input ports, and parameters
  (but not cache entries). This is the default dependency for computations that
  have not specified anything more refined. It is equivalent to the set
  `{all_sources_except_input_ports_ticket(), all_input_ports_ticket()}`.
  @see cache_entry_ticket() to obtain a ticket for a cache entry. */
  static DependencyTicket all_sources_ticket() {
    return DependencyTicket(internal::kAllSourcesTicket);
  }

  /** Returns a ticket indicating dependence on the cache entry indicated
  by `index`. Note that cache entries are _not_ included in the `all_sources`
  ticket so must be listed separately.
  @pre `index` selects an existing cache entry in this System. */
  DependencyTicket cache_entry_ticket(CacheIndex index) const {
    DRAKE_DEMAND(0 <= index && index < num_cache_entries());
    return cache_entries_[index]->ticket();
  }

  /** Returns a ticket indicating dependence on all source values that may
  affect configuration-dependent computations. In particular, this category
  _does not_ include time, generalized velocities v, miscellaneous continuous
  state variables z, or input ports. Generalized coordinates q are included, as
  well as any discrete state variables that have been declared as configuration
  variables, and configuration-affecting parameters. Finally we assume that
  the accuracy setting may affect some configuration-dependent computations.
  Examples: a parameter that affects length may change the computation of an
  end-effector location. A change in accuracy requirement may require
  recomputation of an iterative approximation of contact forces.
  @see kinematics_ticket()

  @note Currently there is no way to declare specific variables and parameters
  to be configuration-affecting so we include all state variables and
  parameters except for state variables v and z. */
  // TODO(sherm1) Remove the above note once #9171 is resolved.
  // The configuration_tracker implementation in ContextBase must be kept
  // up to date with the above API contract.
  static DependencyTicket configuration_ticket() {
    return DependencyTicket(internal::kConfigurationTicket);
  }

  /** Returns a ticket indicating dependence on all source values that may
  affect configuration- or velocity-dependent computations. This ticket depends
  on the configuration_ticket defined above, and adds in velocity-affecting
  source values. This _does not_ include time or input ports.
  @see configuration_ticket()

  @note Currently there is no way to declare specific variables and parameters
  to be configuration- or velocity-affecting so we include all state variables
  and parameters except for state variables z. */
  // TODO(sherm1) Remove the above note once #9171 is resolved.
  static DependencyTicket kinematics_ticket() {
    return DependencyTicket(internal::kKinematicsTicket);
  }

  /** Returns a ticket for the cache entry that holds time derivatives of
  the continuous variables.
  @see EvalTimeDerivatives() */
  static DependencyTicket xcdot_ticket() {
    return DependencyTicket(internal::kXcdotTicket);
  }

  /** Returns a ticket for the cache entry that holds the potential energy
  calculation.
  @see System::EvalPotentialEnergy() */
  static DependencyTicket pe_ticket() {
    return DependencyTicket(internal::kPeTicket);
  }

  /** Returns a ticket for the cache entry that holds the kinetic energy
  calculation.
  @see System::EvalKineticEnergy() */
  static DependencyTicket ke_ticket() {
    return DependencyTicket(internal::kKeTicket);
  }

  /** Returns a ticket for the cache entry that holds the conservative power
  calculation.
  @see System::EvalConservativePower() */
  static DependencyTicket pc_ticket() {
    return DependencyTicket(internal::kPcTicket);
  }

  /** Returns a ticket for the cache entry that holds the non-conservative
  power calculation.
  @see System::EvalNonConservativePower() */
  static DependencyTicket pnc_ticket() {
    return DependencyTicket(internal::kPncTicket);
  }

  /** (Internal use only) Returns a ticket for the cache entry that holds the
  unique periodic discrete update computation.
  @see System::EvalUniquePeriodicDiscreteUpdate() */
  static DependencyTicket xd_unique_periodic_update_ticket() {
    return DependencyTicket(internal::kXdUniquePeriodicUpdateTicket);
  }

  /** (Internal use only) Returns a ticket indicating dependence on the output
  port indicated by `index`. No user-definable quantities in a system can
  meaningfully depend on that system's own output ports.
  @pre `index` selects an existing output port of this System. */
  DependencyTicket output_port_ticket(OutputPortIndex index) const {
    DRAKE_DEMAND(0 <= index && index < num_output_ports());
    return output_ports_[index]->ticket();
  }
  //@}

  /** Returns the number of declared continuous state variables. */
  int num_continuous_states() const {
    return context_sizes_.num_generalized_positions +
           context_sizes_.num_generalized_velocities +
           context_sizes_.num_misc_continuous_states;
  }

  /** Returns the number of declared discrete state groups (each group is
  a vector-valued discrete state variable). */
  int num_discrete_state_groups() const {
    return context_sizes_.num_discrete_state_groups;
  }

  /** Returns the number of declared abstract state variables. */
  int num_abstract_states() const { return context_sizes_.num_abstract_states; }

  /** Returns the number of declared numeric parameters (each of these is
  a vector-valued parameter). */
  int num_numeric_parameter_groups() const {
    return context_sizes_.num_numeric_parameter_groups;
  }

  /** Returns the number of declared abstract parameters. */
  int num_abstract_parameters() const {
    return context_sizes_.num_abstract_parameters;
  }

  /** Returns the size of the implicit time derivatives residual vector.
  By default this is the same as num_continuous_states() but a LeafSystem
  can change it during construction via
  LeafSystem::DeclareImplicitTimeDerivativesResidualSize(). */
  int implicit_time_derivatives_residual_size() const {
    return implicit_time_derivatives_residual_size_.has_value()
               ? *implicit_time_derivatives_residual_size_
               : num_continuous_states();
  }

  // Note that it is extremely unlikely that a Context will have an invalid
  // system id because it is near impossible for a user to create one. We'll
  // just assume it's valid as a precondition on the ValidateContext() methods.
  // In Debug builds this will be reported as an error but otherwise a
  // readable but imperfect "Not created for this System" message will issue
  // if there is no id.

  // @pre both `context` and `this` have valid System Ids.
  /** Checks whether the given context was created for this system.
  @note This method is sufficiently fast for performance sensitive code.
  @throws std::exception if the System Ids don't match. */
  void ValidateContext(const ContextBase& context) const final {
    if (context.get_system_id() != system_id_) {
      ThrowValidateContextMismatch(context);
    }
  }

  // @pre if `context` is non-null, then both `context` and `this` have valid
  //      System Ids.
  /** Checks whether the given context was created for this system.
  @note This method is sufficiently fast for performance sensitive code.
  @throws std::exception if the System Ids don't match.
  @throws std::exception if `context` is null.
  @exclude_from_pydrake_mkdoc{This overload is not bound.} */
  void ValidateContext(const ContextBase* context) const {
    DRAKE_THROW_UNLESS(context != nullptr);
    ValidateContext(*context);
  }

  // In contrast to Contexts it is easier to create some System-related objects
  // without assigning them a valid system id. So for checking arbitrary object
  // types we'll permit the object to have an invalid system id. (We still
  // require that this SystemBase has a valid system id.)

  // @pre `this` System has a valid system id.
  /** Checks whether the given object was created for this system.
  @note This method is sufficiently fast for performance sensitive code.
  @throws std::exception if the System Ids don't match or if `object` doesn't
                         have an associated System Id.
  @throws std::exception if the argument type is a pointer and it is null. */
  template <class Clazz>
  void ValidateCreatedForThisSystem(const Clazz& object) const {
    const internal::SystemId id = [&]() {
      if constexpr (std::is_pointer_v<Clazz>) {
        DRAKE_THROW_UNLESS(object != nullptr);
        return object->get_system_id();
      } else {
        return object.get_system_id();
      }
    }();
    if (!id.is_same_as_valid_id(system_id_))
      ThrowNotCreatedForThisSystem(object, id);
  }

 protected:
  friend class internal::DiagramSystemBaseAttorney;

  /** (Internal use only). */
  SystemBase() = default;

  //============================================================================
  /** @name                    Declare cache entries
  @anchor DeclareCacheEntry_documentation

  Methods in this section are used by derived classes to declare cache entries
  for their own internal computations. (Other cache entries are provided
  automatically for well-known computations such as output ports and time
  derivatives.) Cache entries may contain values of any type, however the type
  for any particular cache entry is fixed after first allocation. Every cache
  entry must have an _allocator_ function `Allocate()` and a _calculator_
  function `Calc()`. `Allocate()` returns an object suitable for holding a value
  of the cache entry. `Calc()` uses the contents of a given Context to produce
  the cache entry's value, which is placed in an object of the type returned by
  `Allocate()`.

  <h4>Prerequisites</h4>

  Correct runtime caching behavior depends critically on understanding the
  dependencies of the cache entry's `Calc()` function (we call those
  "prerequisites"). If none of the prerequisites has changed since the last
  time `Calc()` was invoked to set the cache entry's value, then we don't need
  to perform a potentially expensive recalculation. On the other hand, if any
  of the prerequisites has changed then the current value is invalid and must
  not be used without first recomputing.

  Currently it is not possible for Drake to infer prerequisites accurately and
  automatically from inspection of the `Calc()` implementation. Therefore,
  if you don't say otherwise, Drake will assume `Calc()` is dependent
  on all value sources in the Context, including time, state, input ports,
  parameters, and accuracy. That means the cache entry's value will be
  considered invalid if _any_ of those sources has changed since the last time
  the value was calculated. That is safe, but can result in more computation
  than necessary. If you know that your `Calc()` method has fewer prerequisites,
  you may say so by providing an explicit list in the `prerequisites_of_calc`
  parameter. Every possible prerequisite has a DependencyTicket ("ticket"), and
  the list should consist of tickets. For example, if your calculator depends
  only on time (e.g. `Calc(context)` is `sin(context.get_time())`) then you
  would specify `prerequisites_of_calc={time_ticket()}` here. See
  @ref DependencyTicket_documentation "Dependency tickets" for a list of the
  possible tickets and what they mean.

  @warning It is critical that the prerequisite list you supply be accurate, or
  at least conservative, for correct functioning of the caching system. Drake
  cannot currently detect that a `Calc()` function accesses an undeclared
  prerequisite. Even assuming you have correctly listed the prerequisites, you
  should include a prominent comment in every `Calc()` implementation noting
  that if the implementation is changed then the prerequisite list must be
  updated correspondingly.

  A technique you can use to ensure that prerequisites have been properly
  specified is to make use of the Context's
  @ref drake::systems::ContextBase::DisableCaching "DisableCaching()"
  method, which causes cache values to be recalculated unconditionally. You
  should get identical results with caching enabled or disabled, with speed
  being the only difference. You can also disable caching for individual
  cache entries in a Context, or specify that individual cache entries should
  be disabled by default when they are first allocated.
  @see ContextBase::DisableCaching()
  @see CacheEntry::disable_caching()
  @see CacheEntry::disable_caching_by_default()
  @see LeafOutputPort::disable_caching_by_default()

  <h4>Which signature to use?</h4>

  Although the allocator and calculator functions ultimately satisfy generic
  function signatures defined by a ValueProducer, we provide a variety
  of `DeclareCacheEntry()` signatures here for convenient specification,
  with mapping to the generic form handled invisibly. In particular,
  allocators are most easily defined by providing a model value that can be
  used to construct an allocator that copies the model when a new value
  object is needed. Alternatively a method can be provided that constructs
  a value object when invoked (those methods are conventionally, but not
  necessarily, named `MakeSomething()` where `Something` is replaced by the
  cache entry value type).

  Because cache entry values are ultimately stored in AbstractValue objects,
  the underlying types must be suitable. That means the type must be copy
  constructible or cloneable. For methods below that are not given an explicit
  model value or construction ("make") method, the underlying type must also be
  default constructible.
  @see drake::Value for more about abstract values. */
  //@{

  /// @anchor DeclareCacheEntry_primary
  /** Declares a new %CacheEntry in this System using the most generic form
  of the calculation function. Prefer one of the more convenient signatures
  below if you can. The new cache entry is assigned a unique CacheIndex and
  DependencyTicket, which can be obtained from the returned %CacheEntry.

  @param[in] description
    A human-readable description of this cache entry, most useful for debugging
    and documentation. Not interpreted in any way by Drake; it is retained
    by the cache entry and used to generate the description for the
    corresponding CacheEntryValue in the Context.
  @param[in] value_producer
    Provides the computation that maps from a given Context to the current
    value that this cache entry should have, as well as a way to allocate
    storage prior to the computation.
  @param[in] prerequisites_of_calc
    Provides the DependencyTicket list containing a ticket for _every_ Context
    value on which `calc_function` may depend when it computes its result.
    Defaults to `{all_sources_ticket()}` if unspecified. If the cache value
    is truly independent of the Context (rare!) say so explicitly by providing
    the list `{nothing_ticket()}`; an explicitly empty list `{}` is forbidden.
  @returns a reference to the newly-created %CacheEntry.
  @throws std::exception if given an explicitly empty prerequisite list. */
  CacheEntry& DeclareCacheEntry(
      std::string description, ValueProducer value_producer,
      std::set<DependencyTicket> prerequisites_of_calc = {
          all_sources_ticket()});

  /// @anchor DeclareCacheEntry_model_and_calc
  /** Declares a cache entry by specifying a model value of concrete type
  `ValueType` and a calculator function that is a class member function (method)
  with signature: @code
    void MySystem::CalcCacheValue(const MyContext&, ValueType*) const;
  @endcode
  where `MySystem` is a class derived from `SystemBase`, `MyContext` is a class
  derived from `ContextBase`, and `ValueType` is any concrete type such that
  `Value<ValueType>` is permitted. (The method names are arbitrary.) Template
  arguments will be deduced and do not need to be specified. See the
  @ref DeclareCacheEntry_primary "primary DeclareCacheEntry() signature"
  above for more information about the parameters and behavior.
  @see drake::Value */
  template <class MySystem, class MyContext, typename ValueType>
  CacheEntry& DeclareCacheEntry(
      std::string description, const ValueType& model_value,
      void (MySystem::*calc)(const MyContext&, ValueType*) const,
      std::set<DependencyTicket> prerequisites_of_calc = {
          all_sources_ticket()});

  /// @anchor DeclareCacheEntry_calc_only
  /** Declares a cache entry by specifying only a calculator function that is a
  class member function (method) with signature:
  @code
    void MySystem::CalcCacheValue(const MyContext&, ValueType*) const;
  @endcode
  where `MySystem` is a class derived from `SystemBase` and `MyContext` is a
  class derived from `ContextBase`. `ValueType` is a concrete type such that
  (a) `Value<ValueType>` is permitted, and (b) `ValueType` is default
  constructible. That allows us to create a model value using
  `Value<ValueType>{}` (value initialized so numerical types will be zeroed in
  the model). (The method name is arbitrary.) Template arguments will be
  deduced and do not need to be specified. See the first DeclareCacheEntry()
  signature above for more information about the parameters and behavior.

  @note The default constructor will be called once immediately to create a
  model value, and subsequent allocations will just copy the model value without
  invoking the constructor again. If you want the constructor invoked again at
  each allocation (not common), use one of the other signatures to explicitly
  provide a method for the allocator to call; that method can then invoke
  the `ValueType` default constructor each time it is called.
  @see drake::Value */
  template <class MySystem, class MyContext, typename ValueType>
  CacheEntry& DeclareCacheEntry(
      std::string description,
      void (MySystem::*calc)(const MyContext&, ValueType*) const,
      std::set<DependencyTicket> prerequisites_of_calc = {
          all_sources_ticket()});
  //@}

  // TODO(jwnimmer-tri) This function does not meet the criteria for inline.
  /** (Internal use only) Adds an already-constructed input port to this System.
  Insists that the port already contains a reference to this System, and that
  the port's index is already set to the next available input port index for
  this System, that the port name is unique (just within this System), and that
  the port name is non-empty. */
  // TODO(sherm1) Add check on suitability of `size` parameter for the port's
  // data type.
  void AddInputPort(std::unique_ptr<InputPortBase> port) {
    using internal::PortBaseAttorney;
    DRAKE_DEMAND(port != nullptr);
    DRAKE_DEMAND(&PortBaseAttorney::get_system_interface(*port) == this);
    DRAKE_DEMAND(port->get_index() == num_input_ports());
    DRAKE_DEMAND(!port->get_name().empty());

    // Check that name is unique.
    for (InputPortIndex i{0}; i < num_input_ports(); i++) {
      if (port->get_name() == input_ports_[i]->get_name()) {
        throw std::logic_error(
            fmt::format("System {} already has an input port named {}",
                        GetSystemName(), port->get_name()));
      }
    }

    input_ports_.push_back(std::move(port));
  }

  // TODO(jwnimmer-tri) This function does not meet the criteria for inline.
  /** (Internal use only) Adds an already-constructed output port to this
  System. Insists that the port already contains a reference to this System, and
  that the port's index is already set to the next available output port index
  for this System, and that the name of the port is unique.
  @throws std::exception if the name of the output port is not unique. */
  // TODO(sherm1) Add check on suitability of `size` parameter for the port's
  // data type.
  void AddOutputPort(std::unique_ptr<OutputPortBase> port) {
    using internal::PortBaseAttorney;
    DRAKE_DEMAND(port != nullptr);
    DRAKE_DEMAND(&PortBaseAttorney::get_system_interface(*port) == this);
    DRAKE_DEMAND(port->get_index() == num_output_ports());
    DRAKE_DEMAND(!port->get_name().empty());

    // Check that name is unique.
    for (OutputPortIndex i{0}; i < num_output_ports(); i++) {
      if (port->get_name() == output_ports_[i]->get_name()) {
        throw std::logic_error("System " + GetSystemName() +
                               " already has an output port named " +
                               port->get_name());
      }
    }

    output_ports_.push_back(std::move(port));
  }

  /** (Internal use only) Returns a name for the next input port, using the
  given name if it isn't kUseDefaultName, otherwise making up a name like "u3"
  from the next available input port index.
  @pre `given_name` must not be empty. */
  std::string NextInputPortName(
      std::variant<std::string, UseDefaultName> given_name) const {
    const std::string result =
        given_name == kUseDefaultName
            ? std::string("u") + std::to_string(num_input_ports())
            : std::get<std::string>(std::move(given_name));
    DRAKE_DEMAND(!result.empty());
    return result;
  }

  /** (Internal use only) Returns a name for the next output port, using the
  given name if it isn't kUseDefaultName, otherwise making up a name like "y3"
  from the next available output port index.
  @pre `given_name` must not be empty. */
  std::string NextOutputPortName(
      std::variant<std::string, UseDefaultName> given_name) const {
    const std::string result =
        given_name == kUseDefaultName
            ? std::string("y") + std::to_string(num_output_ports())
            : std::get<std::string>(std::move(given_name));
    DRAKE_DEMAND(!result.empty());
    return result;
  }

  /** (Internal use only) Assigns a ticket to a new discrete variable group
  with the given `index`.
  @pre The supplied index must be the next available one; that is, indexes
       must be assigned sequentially. */
  void AddDiscreteStateGroup(DiscreteStateIndex index) {
    DRAKE_DEMAND(index == discrete_state_tickets_.size());
    DRAKE_DEMAND(index == context_sizes_.num_discrete_state_groups);
    const DependencyTicket ticket(assign_next_dependency_ticket());
    discrete_state_tickets_.push_back(
        {ticket, "discrete state group " + std::to_string(index)});
    ++context_sizes_.num_discrete_state_groups;
  }

  /** (Internal use only) Assigns a ticket to a new abstract state variable with
  the given `index`.
  @pre The supplied index must be the next available one; that is, indexes
       must be assigned sequentially. */
  void AddAbstractState(AbstractStateIndex index) {
    const DependencyTicket ticket(assign_next_dependency_ticket());
    DRAKE_DEMAND(index == abstract_state_tickets_.size());
    DRAKE_DEMAND(index == context_sizes_.num_abstract_states);
    abstract_state_tickets_.push_back(
        {ticket, "abstract state " + std::to_string(index)});
    ++context_sizes_.num_abstract_states;
  }

  /** (Internal use only) Assigns a ticket to a new numeric parameter with
  the given `index`.
  @pre The supplied index must be the next available one; that is, indexes
       must be assigned sequentially. */
  void AddNumericParameter(NumericParameterIndex index) {
    DRAKE_DEMAND(index == numeric_parameter_tickets_.size());
    DRAKE_DEMAND(index == context_sizes_.num_numeric_parameter_groups);
    const DependencyTicket ticket(assign_next_dependency_ticket());
    numeric_parameter_tickets_.push_back(
        {ticket, "numeric parameter " + std::to_string(index)});
    ++context_sizes_.num_numeric_parameter_groups;
  }

  /** (Internal use only) Assigns a ticket to a new abstract parameter with
  the given `index`.
  @pre The supplied index must be the next available one; that is, indexes
       must be assigned sequentially. */
  void AddAbstractParameter(AbstractParameterIndex index) {
    const DependencyTicket ticket(assign_next_dependency_ticket());
    DRAKE_DEMAND(index == abstract_parameter_tickets_.size());
    DRAKE_DEMAND(index == context_sizes_.num_abstract_parameters);
    abstract_parameter_tickets_.push_back(
        {ticket, "abstract parameter " + std::to_string(index)});
    ++context_sizes_.num_abstract_parameters;
  }

  /** (Internal use only) This is for cache entries associated with pre-defined
  tickets, for example the cache entry for time derivatives. See the public API
  for the most-general DeclareCacheEntry() signature for the meanings of the
  other parameters here. */
  CacheEntry& DeclareCacheEntryWithKnownTicket(
      DependencyTicket known_ticket, std::string description,
      ValueProducer value_producer,
      std::set<DependencyTicket> prerequisites_of_calc = {
          all_sources_ticket()});

  /** Returns a pointer to the service interface of the immediately enclosing
  Diagram if one has been set, otherwise nullptr. */
  const internal::SystemParentServiceInterface* get_parent_service() const {
    return parent_service_;
  }

  /** (Internal use only) Assigns the next unused dependency ticket number,
  unique only within a particular system. Each call to this method increments
  the ticket number. */
  DependencyTicket assign_next_dependency_ticket() {
    return next_available_ticket_++;
  }

  /** (Internal use only) Checks if a ticket depends on (any) input port. When
  this returns "true" the ticket MUST NOT depend on input. When this returns
  "false", it just means that we're not sure. This is intended to be an
  inexpensive check, withough searching the entire graph. */
  bool IsObviouslyNotInputDependent(DependencyTicket dependency_ticket) const;

  /** (Internal use only) Declares that `parent_service` is the service
  interface of the Diagram that owns this subsystem. Throws if the parent
  service has already been set and `parent_service` is non-null. */
  // Use static method so Diagram can invoke this on behalf of a child.
  // Output argument is listed first because it is serving as the 'this'
  // pointer here.
  static void set_parent_service(
      SystemBase* child,
      const internal::SystemParentServiceInterface* parent_service);

  /** (Internal use only) Given a `port_index`, returns a function to be called
  when validating Context::FixInputPort requests. The function should attempt
  to throw an exception if the input AbstractValue is invalid, so that errors
  can be reported at Fix-time instead of EvalInput-time.*/
  virtual std::function<void(const AbstractValue&)> MakeFixInputPortTypeChecker(
      InputPortIndex port_index) const = 0;

  /** (Internal use only) Shared code for updating an input port and returning a
  pointer to its abstract value, or nullptr if the port is not connected. `func`
  should be the user-visible API function name obtained with __func__. */
  const AbstractValue* EvalAbstractInputImpl(const char* func,
                                             const ContextBase& context,
                                             InputPortIndex port_index) const;

  /** Throws std::exception to report a negative `port_index` that was
  passed to API method `func`. Caller must ensure that the function name
  makes it clear what kind of port we're complaining about. */
  // We're taking an int here for the index; InputPortIndex and OutputPortIndex
  // can't be negative.
  [[noreturn]] void ThrowNegativePortIndex(const char* func,
                                           int port_index) const;

  /** Throws std::exception to report bad input `port_index` that was passed
  to API method `func`. */
  [[noreturn]] void ThrowInputPortIndexOutOfRange(
      const char* func, InputPortIndex port_index) const;

  /** Throws std::exception to report bad output `port_index` that was passed
  to API method `func`. */
  [[noreturn]] void ThrowOutputPortIndexOutOfRange(
      const char* func, OutputPortIndex port_index) const;

  /** Throws std::exception because someone misused API method `func`, that is
  only allowed for declared-vector input ports, on an abstract port whose
  index is given here. */
  [[noreturn]] void ThrowNotAVectorInputPort(const char* func,
                                             InputPortIndex port_index) const;

  /** Throws std::exception because someone called API method `func` claiming
  the input port had some value type that was wrong. */
  [[noreturn]] void ThrowInputPortHasWrongType(
      const char* func, InputPortIndex port_index,
      const std::string& expected_type, const std::string& actual_type) const;

  // This method is static for use from outside the System hierarchy but where
  // the problematic System is clear.
  /** Throws std::exception because someone called API method `func` claiming
  the input port had some value type that was wrong. */
  [[noreturn]] static void ThrowInputPortHasWrongType(
      const char* func, const std::string& system_pathname, InputPortIndex,
      const std::string& port_name, const std::string& expected_type,
      const std::string& actual_type);

  /** Throws std::exception because someone called API method `func`, that
  requires this input port to be evaluatable, but the port was neither
  fixed nor connected. */
  [[noreturn]] void ThrowCantEvaluateInputPort(const char* func,
                                               InputPortIndex port_index) const;

  /** (Internal use only) Returns the InputPortBase at index `port_index`,
  throwing std::exception we don't like the port index. The name of the
  public API method that received the bad index is provided in `func` and is
  included in the error message. The `warn_deprecated` governs whether or not
  a deprecation warning should occur when the `port_index` is deprecated; calls
  made on behalf of the user should pass `true`; calls made on behalf or the
  framework internals should pass `false`. */
  const InputPortBase& GetInputPortBaseOrThrow(const char* func, int port_index,
                                               bool warn_deprecated) const {
    if (port_index < 0) {
      ThrowNegativePortIndex(func, port_index);
    }
    const InputPortIndex port(port_index);
    if (port_index >= num_input_ports())
      ThrowInputPortIndexOutOfRange(func, port);
    if (warn_deprecated && input_ports_[port]->get_deprecation().has_value())
      WarnPortDeprecation(/* is_input = */ true, port_index);
    return *input_ports_[port];
  }

  /** (Internal use only) Returns the OutputPortBase at index `port_index`,
  throwing std::exception if we don't like the port index. The name of the
  public API method that received the bad index is provided in `func` and is
  included in the error message. The `warn_deprecated` governs whether or not
  a deprecation warning should occur when the `port_index` is deprecated; calls
  made on behalf of the user should pass `true`; calls made on behalf or the
  framework internals should pass `false`. */
  const OutputPortBase& GetOutputPortBaseOrThrow(const char* func,
                                                 int port_index,
                                                 bool warn_deprecated) const {
    if (port_index < 0) {
      ThrowNegativePortIndex(func, port_index);
    }
    const OutputPortIndex port(port_index);
    if (port_index >= num_output_ports())
      ThrowOutputPortIndexOutOfRange(func, port);
    if (warn_deprecated && output_ports_[port]->get_deprecation().has_value())
      WarnPortDeprecation(/* is_input = */ false, port_index);
    return *output_ports_[port_index];
  }

  /** (Internal use only) Throws std::exception with a message that the sanity
  check(s) given by ValidateContext have failed. */
  [[noreturn]] void ThrowValidateContextMismatch(const ContextBase&) const;

  /** (Internal use only) Returns the message to use for a std::exception in
  the case of unsupported scalar type conversions. */
  virtual std::string GetUnsupportedScalarConversionMessage(
      const std::type_info& source_type,
      const std::type_info& destination_type) const;

  /** This method must be invoked from within derived class DoAllocateContext()
  implementations right after the concrete Context object has been allocated.
  It allocates cache entries, sets up all intra-Context dependencies, and marks
  the ContextBase as initialized so that we can verify proper derived-class
  behavior.
  @pre The supplied context must not be null and must not already have been
       initialized. */
  void InitializeContextBase(ContextBase* context) const;

  /** Derived class implementations should allocate a suitable concrete Context
  type, then invoke the above InitializeContextBase() method. A Diagram must
  then invoke AllocateContext() to obtain each of the subcontexts for its
  DiagramContext, and must set up inter-subcontext dependencies among its
  children and between itself and its children. Then context resources such as
  parameters and state should be allocated. */
  virtual std::unique_ptr<ContextBase> DoAllocateContext() const = 0;

  /** Return type for get_context_sizes(). Initialized to zero
  and equipped with a += operator for Diagram use in aggregation. */
  struct ContextSizes {
    int num_generalized_positions{0};   // q }
    int num_generalized_velocities{0};  // v | Sum is num continuous states x.
    int num_misc_continuous_states{0};  // z }
    int num_discrete_state_groups{0};   // Each "group" is a vector.
    int num_abstract_states{0};
    int num_numeric_parameter_groups{0};  // Each "group" is a vector.
    int num_abstract_parameters{0};

    ContextSizes& operator+=(const ContextSizes& other) {
      num_generalized_positions += other.num_generalized_positions;
      num_generalized_velocities += other.num_generalized_velocities;
      num_misc_continuous_states += other.num_misc_continuous_states;
      num_discrete_state_groups += other.num_discrete_state_groups;
      num_abstract_states += other.num_abstract_states;
      num_numeric_parameter_groups += other.num_numeric_parameter_groups;
      num_abstract_parameters += other.num_abstract_parameters;
      return *this;
    }
  };

  /** Obtains access to the declared Context partition sizes as accumulated
  during LeafSystem or Diagram construction .*/
  const ContextSizes& get_context_sizes() const { return context_sizes_; }

  /** Obtains mutable access to the Context sizes struct. Should be used only
  during LeafSystem or Diagram construction. */
  ContextSizes& get_mutable_context_sizes() { return context_sizes_; }

  /** Allows Diagram to access protected get_context_sizes() recursively on its
  subsystems. */
  static const ContextSizes& get_context_sizes(const SystemBase& system) {
    return system.get_context_sizes();
  }

  /** Allows a LeafSystem to override the default size for the implicit time
  derivatives residual and a Diagram to sum up the total size. If no value
  is set, the default size is n=num_continuous_states().

  @param[in] n The size of the residual vector output argument of
               System::CalcImplicitTimeDerivativesResidual(). If n <= 0
               restore to the default, num_continuous_states().

  @see implicit_time_derivatives_residual_size()
  @see LeafSystem::DeclareImplicitTimeDerivativesResidualSize()
  @see System::CalcImplicitTimeDerivativesResidual() */
  void set_implicit_time_derivatives_residual_size(int n) {
    implicit_time_derivatives_residual_size_.reset();
    if (n > 0) {
      implicit_time_derivatives_residual_size_ = n;
    }
  }

  /** (Internal) Gets the id used to tag context data as being created by this
  system. See @ref system_compatibility. */
  internal::SystemId get_system_id() const { return system_id_; }

  /** The NVI implementation of GetGraphvizFragment() for subclasses to override
  if desired. The default behavior should be sufficient in most cases. */
  virtual GraphvizFragment DoGetGraphvizFragment(
      const GraphvizFragmentParams& params) const;

 private:
  void CreateSourceTrackers(ContextBase*) const;

  static internal::SystemId get_next_id();

  // Used to create trackers for variable-number System-allocated objects.
  struct TrackerInfo {
    DependencyTicket ticket;
    std::string description;
  };

  const TrackerInfo& discrete_state_tracker_info(
      DiscreteStateIndex index) const {
    DRAKE_DEMAND(0 <= index && index < discrete_state_tickets_.size());
    return discrete_state_tickets_[index];
  }

  const TrackerInfo& abstract_state_tracker_info(
      AbstractStateIndex index) const {
    DRAKE_DEMAND(0 <= index && index < abstract_state_tickets_.size());
    return abstract_state_tickets_[index];
  }

  const TrackerInfo& numeric_parameter_tracker_info(
      NumericParameterIndex index) const {
    DRAKE_DEMAND(0 <= index && index < numeric_parameter_tickets_.size());
    return numeric_parameter_tickets_[index];
  }

  const TrackerInfo& abstract_parameter_tracker_info(
      AbstractParameterIndex index) const {
    DRAKE_DEMAND(0 <= index && index < abstract_parameter_tickets_.size());
    return abstract_parameter_tickets_[index];
  }

  template <class Clazz>
  [[noreturn]] void ThrowNotCreatedForThisSystem(const Clazz& object,
                                                 internal::SystemId id) const {
    unused(object);
    ThrowNotCreatedForThisSystemImpl(
        NiceTypeName::Get<std::remove_pointer_t<Clazz>>(), id);
  }

  [[noreturn]] void ThrowNotCreatedForThisSystemImpl(
      const std::string& nice_type_name, internal::SystemId id) const;

  // Given a deprecated input or output port (as determined by `input`), logs
  // a warning (just once per process) about the deprecation.
  void WarnPortDeprecation(bool is_input, int port_index) const;

  // Returns the names of all input (or output) ports, styled for Graphviz using
  // its subset of HTML. For example, this handles the markup for deprecated or
  // size-0 ports as documented in GetGraphvizString(). The return value is in
  // port-index order -- the i'th element is the i'th input (or output) port.
  std::vector<std::string> GetGraphvizPortLabels(bool input) const;

  // Ports and cache entries hold their own DependencyTickets. Note that the
  // addresses of the elements are stable even if the std::vectors are resized.

  // Indexed by InputPortIndex.
  std::vector<std::unique_ptr<InputPortBase>> input_ports_;
  // Indexed by OutputPortIndex.
  std::vector<std::unique_ptr<OutputPortBase>> output_ports_;
  // Indexed by CacheIndex.
  std::vector<std::unique_ptr<CacheEntry>> cache_entries_;

  // States and parameters don't hold their own tickets so we track them here.

  // Indexed by DiscreteStateIndex.
  std::vector<TrackerInfo> discrete_state_tickets_;
  // Indexed by AbstractStateIndex.
  std::vector<TrackerInfo> abstract_state_tickets_;
  // Indexed by NumericParameterIndex.
  std::vector<TrackerInfo> numeric_parameter_tickets_;
  // Indexed by AbstractParameterIndex.
  std::vector<TrackerInfo> abstract_parameter_tickets_;

  // Initialize to the first ticket number available after all the well-known
  // ones. This gets incremented as tickets are handed out for the optional
  // entities above.
  DependencyTicket next_available_ticket_{internal::kNextAvailableTicket};

  // The enclosing Diagram. Null/invalid when this is the root system.
  const internal::SystemParentServiceInterface* parent_service_{nullptr};

  // Name of this system.
  std::string name_;

  // Unique id of this system.
  internal::SystemId system_id_{get_next_id()};

  // Records the total sizes of Context resources as they will appear
  // in a Context allocated by this System. Starts at zero, counts up during
  // declaration for LeafSystem construction; computed recursively during
  // Diagram construction.
  ContextSizes context_sizes_;

  // Defaults to num_continuous_states() if no value here. Diagrams always
  // fill this in by summing the value from their immediate subsystems.
  std::optional<int> implicit_time_derivatives_residual_size_;
};

// Implementations of templatized DeclareCacheEntry() methods.

// Takes an initial value and calc() member function that has an output
// argument.
template <class MySystem, class MyContext, typename ValueType>
CacheEntry& SystemBase::DeclareCacheEntry(
    std::string description, const ValueType& model_value,
    void (MySystem::*calc)(const MyContext&, ValueType*) const,
    std::set<DependencyTicket> prerequisites_of_calc) {
  static_assert(std::is_base_of_v<SystemBase, MySystem>,
                "Expected to be invoked from a SystemBase-derived System.");
  static_assert(std::is_base_of_v<ContextBase, MyContext>,
                "Expected to be invoked with a ContextBase-derived Context.");
  auto& entry = DeclareCacheEntry(std::move(description),
                                  ValueProducer(this, model_value, calc),
                                  std::move(prerequisites_of_calc));
  return entry;
}

// Takes just a calc() member function with an output argument, and
// value-initializes the entry.
template <class MySystem, class MyContext, typename ValueType>
CacheEntry& SystemBase::DeclareCacheEntry(
    std::string description,
    void (MySystem::*calc)(const MyContext&, ValueType*) const,
    std::set<DependencyTicket> prerequisites_of_calc) {
  static_assert(
      std::is_default_constructible_v<ValueType>,
      "SystemBase::DeclareCacheEntry(calc): the calc-only overloads of "
      "this method requires that the output type has a default constructor");
  // Invokes the above model-value method. Note that value initialization {}
  // is required here.
  return DeclareCacheEntry(std::move(description), ValueType{}, calc,
                           std::move(prerequisites_of_calc));
}

#ifndef DRAKE_DOXYGEN_CXX
template <typename>
class Diagram;
namespace internal {
// This is an attorney-client pattern class providing Diagram with access to
// certain specific SystemBase protected member functions, and nothing else.
// Without this, Diagram couldn't call protected member functions on instances
// other than itself.
class DiagramSystemBaseAttorney {
 public:
  DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(DiagramSystemBaseAttorney);
  DiagramSystemBaseAttorney() = delete;

 private:
  template <typename>
  friend class drake::systems::Diagram;

  static std::string GetUnsupportedScalarConversionMessage(
      const SystemBase&, const std::type_info&, const std::type_info&);

  static std::vector<std::string> GetGraphvizPortLabels(const SystemBase&,
                                                        bool input);
};
}  // namespace internal
#endif

}  // namespace systems
}  // namespace drake
